/*
The allocator.c file implements the lowest level memory allocator
*/


#include <globals.h>
#include <multiboot.h>
#include <panic.h>

//tmp
//#include <klib.h>

//Private Includes
#include <vmm_const.h>
#include <allocator.h>


/* Redefine EXTERN because we'll be defining functions for real here */ 


/* Data structures */

                                /* System Memory Pages */
dword_t nr_pages;               /* Number of memory pages above the 16 MB mark */


                                /* Memory bitmaps */
byte_t *bitmap_start;           /* Bitmap for the low memory page allocation */
byte_t *bitmap_end;             /* Pointer to the end of the memory page bitmap */


/* Private functions prototypes */


void ivmmBitmap_setBit(dword_t page,int state);
int ivmmBitmap_getBit(dword_t page);
dword_t ivmmAddressToPage(dword_t address);
dword_t ivmmPageToAddress(dword_t page);
dword_t ivmmGetFreePage(void);

/* Functions implementation */

void ivmmBitmap_setBit(dword_t page,int state)
  {
    byte_t *p,mask;
    dword_t byte,bit;     

    //printf("page=%d -- nrpages=%d\n",page,nr_pages_all);

    //Check page validity
    //if (page<0 || page>nr_pages_all)
    if (page>nr_pages)
      {
	//printf("page=%x of %x",page,nr_pages_all);
	//printf("Err: page: %x out of %x",page,nr_pages);
	panic("allocator: Invalid memory page allocation.");  // invalid page requested
      }
    
    // The parameters have been set properly, so mangle with the bits

    p=bitmap_start;                            // Point to the beginning of the structure

    byte=page/8;                             // Byte which contains the page's reference bit
    bit=page%8;                              // the bit to consider inside that byte
   
    p+=byte;                                 // pointer to the n-th Byte of the bitmap
    mask=1<<bit;                             // prepare a mask to consider the proper bit 
    
    *p = state ? *p | mask : *p & ~mask;     // either set or clear that bit, depending on the "state" parameter    
  }


int ivmmBitmap_getBit(dword_t page)
  {
    byte_t *p,mask;
    dword_t byte,bit;     

    //Check page validity
    //if (page<0 || page>nr_pages_all)
    if (page>nr_pages)
      panic("allocator: Invalid memory page request.");  // invalid page requested
    
    // The parameters have been set properly, so mangle with the bits

    p=bitmap_start;                            // Point to the beginning of the structure

    byte=page/8;                             // Byte which contains the page's reference bit
    bit=page%8;                              // the bit to consider inside that byte
   
    p+=byte;                                 // pointer to the n-th Byte of the bitmap
    mask=1<<bit;                             // prepare a mask to consider the proper bit 

    return (*p & mask)>>bit;                 // return either 0 or 1 (value of the proper bit shifted to the LSB)
  }

dword_t ivmmAddressToPage(dword_t address)
  {    
    //if (address%MEM_PAGE_SIZE)
    //  panic("allocator: Passing non 4K aligned address.");

    //return (dword_t)(address/MEM_PAGE_SIZE);
    return address>>12;   // 12 bits are page offset (4 KB page)
  }

dword_t ivmmPageToAddress(dword_t page)
  {    
    //return (dword_t)(page*MEM_PAGE_SIZE);
    return page<<12;
  }

dword_t ivmmGetFreePage(void)
  {
    dword_t i;
    
    //start from first page above 1MB
    for (i=MEM_1MB_PAGE;i<nr_pages;i++)
      if (ivmmBitmap_getBit(i)==ALLOCATOR_PAGE_FREE)
	return i;
    return 0;
  }


/* Public Functions implementation */

void ivmmAllocator_initialize(void)
  {
    unsigned long pg;
    dword_t pg_first,span;
    unsigned int tmpbytes;
    dword_t i;

    // We know how much memory the system has got
    // it was calculated while examining the multiboot structure
    // the variable 'mem_tot' expresses it.    

    //Calculate how many memory pages there are
    // mem_tot is expressed in KB
    nr_pages=mem_tot/4;

    ///////
    //printf("nr_pages=%d\n",(int)nr_pages);
    
    //Create bitmap at end of kernel (we allocate it there since we don't have malloc() yet)
    bitmap_start=((byte_t *)Kernel_End)+1;        //Place low_mem bitmap at end of kernel +1 Byte (for safety)

    //Length is the size in Bytes of the bitmap
    tmpbytes=nr_pages/8;
    if (nr_pages % 8)
      tmpbytes++;

    bitmap_end=bitmap_start+tmpbytes;            //Pointer to the end of the bitmap 
   
    //Now we have to increment the kernel size by the size of the bitmap, because the memory allocator is part of the kernel
    Kernel_End=(dword_t)bitmap_end;


    //Let's initialize all pages to busy
    for (i=0;i<nr_pages;i++)  
      ivmmBitmap_setBit(i,ALLOCATOR_PAGE_BUSY);

    // Set free regions as free
    if (mmap_valid==1)
      {
	// Memory map is available, use it 
	struct Multiboot_Info_T *mbi;
	struct SysMemMap_T *mmap;
	qword_t addr;

	mbi=(struct Multiboot_Info_T *)GRUB_boot_header;

	for (i=0;i<mbi->mmap_length;i+=mmap->size+sizeof(mmap->size))
	  {
	    //calculate current entry
	    mmap=(struct SysMemMap_T *)((unsigned long)mbi->mmap_addr+i);	

	    // if region is available then mark it as free
	    if (mmap->type==SMM_AVAILABLE)
	      {
		addr=(((qword_t)mmap->base_addr_high)<<32)+mmap->base_addr_low;
		pg_first=addr/4096;  // retrieve starting page number
		
		addr=(((qword_t)mmap->bytes_high)<<32)+mmap->bytes_low;
		// addr now contains the size in Bytes of the memory area
		span=addr/4096;  // calculate length in pages
		
		// set pages as free
		for (pg=0;pg<span;pg++)
		  ivmmBitmap_setBit(pg_first+pg,ALLOCATOR_PAGE_FREE);
	      }	    
	  }
      }
    else
      {
	// Memory map is not available, use a standard approach:
	// mark mem_lower and mem_upper free

	pg_first=0;	       // first page
	span=mem_lower/4;      // number of lower memory pages (< 1 MB)

	// set pages as free
	for (pg=0;pg<span;pg++)
	  ivmmBitmap_setBit(pg_first+pg,ALLOCATOR_PAGE_FREE);	

	
	pg_first=256;          // start from 1 MB
	span=mem_upper/4;      // number of upper memory pages (> 1 MB)

	// set pages as free
	for (pg=0;pg<span;pg++)
	  ivmmBitmap_setBit(pg_first+pg,ALLOCATOR_PAGE_FREE);	
      }
        
    
    // Mark some known areas as busy
    // Note that NOBODY, EVER, will ask to deallocate them, because the higher level memory manager will only allow deallocation of previously allocated pages, but these won't be allocated, ever.


    //Mark the IDT and GDT space as busy (page 0)
    //ivmmBitmap_setBit(0,ALLOCATOR_PAGE_BUSY);
    
    //printf("\nKernel Start = %x \n",(int)Kernel_Start);
    //printf("\nKernel End   = %x \n",(int)Kernel_End);


    //Mark kernel space as busy 
    pg_first=(Kernel_Start-RELOC_PATCH)/4096;
    span=(Kernel_End-Kernel_Start+1)/4096;
    //if ((Kernel_End-Kernel_Start)%4096)
    if (span%4096)
      span++;
    for (pg=0;pg<span;pg++)
      ivmmBitmap_setBit(pg_first+pg,ALLOCATOR_PAGE_BUSY);	
  }

dword_t ivmmAllocator_AllocatePage(void)
  {
    dword_t page;

    //get a free page in low memory
    page=ivmmGetFreePage();
    if (page==0)  //if there are none left then panic
      panic("allocator: not enough memory for page allocation");

    //mark that page as busy
    ivmmBitmap_setBit(page,ALLOCATOR_PAGE_BUSY);  

    //return the page address
    return ivmmPageToAddress(page);   
  }

void ivmmAllocator_DeallocatePage(dword_t address)
  {
    /*
      Note: It'd be useless to check (at this level) if the page 
      is already free...
      This way we save some code, and the result is the same,
      ie you free a free page, which remains free.
      This may seem a bit time consuming, but checking whether or not
      the page is free already would require the same bit mangling
      therefore the same amount of time.
     */

    // Note #2: Hopefully though, the fact of freeing a free page will never happen. I'll check this when the system is a bit more mature.

    //translate the address into a page and mark it free, no matter what
    ivmmBitmap_setBit(ivmmAddressToPage(address),ALLOCATOR_PAGE_FREE);
  }

void ivmmAllocator_setPageBusy(dword_t address)
  {
    dword_t page;

    page=ivmmAddressToPage(address);
    ivmmBitmap_setBit(page,ALLOCATOR_PAGE_BUSY);
  }

void ivmmAllocator_setPageFree(dword_t address)
  {
    dword_t page;

    page=ivmmAddressToPage(address);
    ivmmBitmap_setBit(page,ALLOCATOR_PAGE_FREE);
  }

